package com.lobstar.xrpc.server.main;

import java.io.Closeable;
import java.io.IOException;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.lobstar.xrpc.common.ResponseHandler;
import com.lobstar.xrpc.common.ServerCallable;
import com.lobstar.xrpc.common.TaskExecutable;
import com.lobstar.xrpc.common.po.SMessage;
import com.lobstar.xrpc.common.po.Task;
import com.lobstar.xrpc.server.handler.ServerInitializer;
import com.lobstar.xrpc.server.handler.task.TaskCallback;
import com.lobstar.xrpc.server.listener.OfflineListener;
import com.lobstar.xrpc.server.listener.OnlineListener;
import com.lobstar.xrpc.server.listener.ServerListeners;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import io.netty.util.concurrent.GlobalEventExecutor;

public class MainServer extends TaskExecutable implements ServerCallable,Closeable{
	private static final Logger LOGGER = LoggerFactory.getLogger(MainServer.class);

	private boolean online;
	private int port;
	private int workerNum = 10;
	private ServerListeners listeners = ServerListeners.defaultListeners();

	private ChannelGroup group = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
	private ExecutorService serverRunner = Executors.newFixedThreadPool(1);
	private Map<String, Channel> onlineMap = new ConcurrentHashMap<>();

	private EventLoopGroup bossGroup;
	private EventLoopGroup workerGroup;
	private MainServer(int port,String basePackage) {
		super(basePackage,10,100,10);
		this.port = port;
	}
	private MainServer(int port,String basePackage,int taskExecutorSize,int responseCacheSize,int responseCacheExpire) {
		super(basePackage,taskExecutorSize,responseCacheSize,responseCacheExpire);
		this.port = port;
	}
	public static MainServer build(int port,String basePackage) {
		return new MainServer(port,basePackage);
	}
	private void open() {
		bossGroup = new NioEventLoopGroup(4);
		workerGroup = new NioEventLoopGroup(10);
		ChannelFuture f ;
	    ServerBootstrap b ;
	    try {
			b = new ServerBootstrap();
			b.group(bossGroup, workerGroup);
			b.channel(NioServerSocketChannel.class);
			b.childHandler(new ServerInitializer(this));
			// 服务器绑定端口监听
			f = b.bind(port).sync();
			// 监听服务器关闭监听，此方法会阻塞
			f.addListener(new GenericFutureListener<Future<? super Void>>() {
				@Override
				public void operationComplete(Future<? super Void> future) throws Exception {
					if(future.isSuccess()) {
						online = true;
						System.out.println("建立成功");
					}
				}
			});
			f.channel().closeFuture().sync();
			online = false;
		} catch (InterruptedException e) {
			LOGGER.error("server start error",e);
		} finally {
			bossGroup.shutdownGracefully();
			workerGroup.shutdownGracefully();
		}
	}
	public MainServer start() {
		serverRunner.execute(new Runnable() {
			@Override
			public void run() {
				open();
			}
		});
		return this;
	}
	public MainServer blockStart() throws InterruptedException {
		start();
		int wait = 100;
		while(!online) {
			--wait;
			if(wait==0) {break;}
			Thread.sleep(100);
		}
		if(!online) {
			throw new RuntimeException("connot connect server");
		}
		return this;
	}
	public void call(String target,SMessage message,TaskCallback callback) {
		Task task = new Task();
		task.setRequest(message);
		task.setTarget(target);
		task.setCallback(callback);
		task.setId(message.getId());
		String id = task.getId();
		cacheTask(id, task);
		sendMessage(target,task.getRequest());
	}
	 public boolean isOnline(String target) {
		 return getChannel(target)!=null;
	 }
	private String sendMessage(String target,SMessage message) {
		if(message == null) {
			throw new RuntimeException("cannot sent null message");
		}
		ChannelFuture future = getChannel(target).writeAndFlush(message);
		future.addListener((Future<? super Void> f) -> {
				 if(f.isSuccess()) {
					 	//sendListener.success(message);
		            } else {
		            	//sendListener.fail(message);
		            }
		});
		return message.getId();
	}

	private Channel getChannel(String id) {
		return onlineMap.get(id);
	}

	public Set<String> getOnlineIdSet() {
		LOGGER.info("online:{},map:{}",onlineMap.size(),onlineMap.keySet());
		return onlineMap.keySet();
	}

	public ServerListeners getListeners() {
		return listeners;
	}
	public MainServer setListeners(ServerListeners listeners) {
		this.listeners = listeners;
		return this;
	}

	public MainServer addOnlineListener(OnlineListener onlineListener) {
		this.listeners.setOnlineListener(onlineListener);
		return this;
	}
	public MainServer addOfflineListener(OfflineListener offlineListener) {
		this.listeners.setOfflineListener(offlineListener);
		return this;
	}

	public ChannelGroup getGroup() {
		return group;
	}
	public void setGroup(ChannelGroup group) {
		this.group = group;
	}
	public Map<String, Channel> getOnlineMap() {
		return onlineMap;
	}
	public void setOnlineMap(Map<String, Channel> onlineMap) {
		this.onlineMap = onlineMap;
	}
	public int getWorkerNum() {
		return workerNum;
	}
	public void setWorkerNum(int workerNum) {
		this.workerNum = workerNum;
	}
	public ResponseHandler getResponseHandler() {
		return responseHandler;
	}
	public void setResponseHandler(ResponseHandler responseHandler) {
		this.responseHandler = responseHandler;
	}
	@Override
	public boolean online(String targetClient) {
		return online&&isOnline(targetClient);
	}
	@Override
	public void close() throws IOException {
		Set<Entry<String,Channel>> entrySet = onlineMap.entrySet();
		for (Entry<String, Channel> entry : entrySet) {
			entry.getValue().close();
		}
		workerGroup.shutdownGracefully();
		bossGroup.shutdownGracefully();
		serverRunner.shutdown();
		online = false;
	}

}
